/*
 * Copyright (c) 2009, The MilkyTracker Team.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * - Neither the name of the <ORGANIZATION> nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *  LoaderDBM.cpp
 *  MilkyPlay Module Loader: Digibooster Pro
 *
 *  Created by Peter Barth on 06.03.06.
 *
 */

#include "Loaders.h"

// to-do: improve detection
const char* LoaderDBM::identifyModule(const mp_ubyte* buffer)
{
    // check for .DTM module
    if (memcmp(buffer, "DBM0", 4))
        return NULL;

    return "DBM";
}

#define CLEAN_DBM \
    { \
        mp_uint32 i; \
        delete[] srcIns; \
        for (i = 0; i < header->smpnum; i++) \
            delete[] srcSmp[i].sample; \
        delete[] srcSmp; \
        delete[] orderListLengths; \
        for (i = 0; i < numSubSongs; i++) \
            delete[] orderLists[i]; \
        for (i = 0; i < header->patnum; i++) \
            delete[] patterns[i]; \
        delete[] orderLists; \
        delete[] patterns; \
    }


struct DBMInstrument
{
    mp_ubyte name[30];
    mp_uword sampnum;
    mp_uword volume;
    mp_uint32 finetune;
    mp_uint32 repstart;
    mp_uint32 replen;
    mp_uword panning;
    mp_uword flags;
    // added by ME
    mp_sint32 venvnum;
    mp_sint32 penvnum;
};

struct DBMSample
{
    mp_uint32 flags;
    mp_uint32 samplen;   // bit 0 set - 8 bit sample
                         // bit 1 set - 16 bit sample
                         // bit 2 set - 32 bit sample
    mp_ubyte* sample;
};

static void convertDBMffects(mp_ubyte& effect, mp_ubyte& operand)
{
    //static const char eff[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};

    switch (effect)
    {
        case 0x00:
            if (operand)
                effect = 0x20;
            break;

        case 0x01:
            if ((operand & 0xF0) == 0xF0)
            {
                // doc says: 1Fx == fine portamento up
                effect = 0x31;
                operand &= 0x0F;
            }
            break;
        case 0x02:
            if ((operand & 0xF0) == 0xF0)
            {
                // doc says: 2Fx == fine portamento down
                effect = 0x32;
                operand &= 0x0F;
            }
            break;

        case 0x03:
        case 0x04:
            break;

        case 0x05:
            if ((operand & 0x0F) == 0x0F)
            {
                // doc says: 5xF == Tone portamento+Fine Volume slide up
                // !! unsupported !!
                // Might be possible with MDL way of combining portamento + volume slide
                // but with S3M volslide instead of MDL volslide
                goto missing;
            }
            else if ((operand & 0xF0) == 0xF0)
            {
                // doc says: 5Fx == Tone portamento+Fine Volume slide down
                // !! unsupported !!
                // Might be possible with MDL way of combining portamento + volume slide
                // but with S3M volslide instead of MDL volslide
                goto missing;
            }
            break;

        case 0x06:
            if ((operand & 0x0F) == 0x0F)
            {
                // doc says: 6xF == Vibrato+Fine Volume slide up
                // !! unsupported !!
                // Might be possible with MDL way of combining vibrato + volume slide
                // but with S3M volslide instead of MDL volslide
                goto missing;
            }
            else if ((operand & 0xF0) == 0xF0)
            {
                // doc says: 6Fx == Vibrato+Fine Volume slide down
                // !! unsupported !!
                // Might be possible with MDL way of combining vibrato + volume slide
                // but with S3M volslide instead of MDL volslide
                goto missing;
            }
            break;

        // Tremolo doesn't exist, huh?
        case 0x07:
        // Panning
        case 0x08:
        // Sample offset
        case 0x09:
            break;

        // Volslide (S3M volslide fits best probably)
        case 0x0A:
            effect = 0x49;
            break;

        // Position jump
        case 0x0B:
            break;

        // set volume
        case 0x0C:
        // set global volume
        case 0x10:
            operand = XModule::vol64to255(operand);
            break;

        // Pattern break
        case 0x0D:
            break;

        // subcommands
        case 0x0E:
        {
            effect = 0x30 + (operand >> 4);
            operand &= 0xF;
            switch (effect)
            {
                // Set filter: unsupported
                case 0x30:
                    goto missing;
                    break;
                // Play backwards
                case 0x33:
                    // AMS can do that
                    effect = 0x4F;
                    operand = 1;
                    break;
                // Turn off sound in channel?
                // can probably be emulated with AMS set channel volume
                // right now unsupported
                case 0x34:
                    goto missing;
                    break;
                // Turn on/off sound in channel?
                // can probably be emulated with AMS set channel volume
                // right now unsupported
                case 0x35:
                    goto missing;
                    break;
                // set loop/loop
                case 0x36:
                    goto missing;
                    break;
                // set offset for loop or what?
                case 0x37:
                    goto missing;
                    break;
            }
            break;
        }

        // Pattern break
        case 0x0F:
            break;

        case 0x11:
            break;

        // key off
        case 0x14:
            if (operand == 0)
                // AMS key of at tick
                effect = 0x51;
            break;

        // Set envelope position
        case 0x15:
            break;

        // Sample offset slide (unsupported)
        case 0x18:
            goto missing;
            break;

        // set real BPM
        case 0x1C:
            effect = 0x52;
            break;

        // echo effects (unsupported)
        case 0x1f:
        case 0x20:
        case 0x21:
        case 0x22:
        case 0x23:
            goto missing;
            break;

        default:
            #ifdef VERBOSE
            printf("Unknown DBM effect %x with operand %x\n", effect, operand);
            #endif
missing:
            #ifdef VERBOSE
            printf("Missing DBM effect %x with operand %x\n", effect, operand);
            #endif
            effect = operand = 0;

    }

}

static void convertDBMEnvelope(TEnvelope& outEnv, const mp_ubyte* inEnv)
{
    memset(&outEnv, 0, sizeof(TEnvelope));
    outEnv.type = *inEnv & 7;

    #ifdef VERBOSE
    if (*inEnv >> 3)
    {
        printf("Second sustain point used");

    }
    #endif

    // one single point?
    // disable envelope
    if (!*(inEnv + 1))
    {
        outEnv.type &= ~1;
        return;
    }

    // DBM stores numpoints-1
    outEnv.num = *(inEnv + 1) + 1;
    outEnv.sustain = *(inEnv + 2);
    outEnv.loops = *(inEnv + 3);
    outEnv.loope = *(inEnv + 4);

    inEnv += 6;

    for (mp_sint32 i = 0; i < outEnv.num; i++)
    {
        outEnv.env[i][0] = BigEndian::GET_WORD(inEnv);
        outEnv.env[i][1] = BigEndian::GET_WORD(inEnv + 2) << 2;
        inEnv += 4;
    }
}

mp_sint32 LoaderDBM::load(XMFileBase& f, XModule* module)
{
    module->cleanUp();

    // this will make code much easier to read
    TXMHeader* header = &module->header;
    TXMInstrument* instr = module->instr;
    TXMSample* smp = module->smp;
    TXMPattern* phead = module->phead;

    // we're already out of memory here
    if (!phead || !instr || !smp)
        return -7;

    mp_sint32 i, j;

    mp_uword numSubSongs = 0;
    mp_uword** orderLists = NULL;
    mp_uword* orderListLengths = NULL;
    DBMInstrument* srcIns = NULL;
    mp_ubyte** patterns = NULL;
    DBMSample* srcSmp = NULL;

    while (true)
    {
        mp_ubyte ID[4], buffer[4];

        mp_uint32 bytesRead = f.read(ID, 4, 1);

        if (bytesRead != 4)
            break;

        bytesRead = f.read(buffer, 4, 1);

        if (bytesRead != 4)
            break;

        mp_uint32 chunkLen = BigEndian::GET_DWORD(buffer);

        switch (BigEndian::GET_DWORD(ID))
        {
            case 0x44424D30:    // 'DBM0'
                memcpy(header->sig, ID, 4);
                break;

            case 0x4E414D45:    // 'NAME'
            {
                mp_ubyte* name = new mp_ubyte[chunkLen];
                f.read(name, 1, chunkLen);
                memcpy(header->name, name, chunkLen > MP_MAXTEXT ? MP_MAXTEXT : chunkLen);
                delete[] name;
                break;
            }

            case 0x494E464F:    // 'INFO'
                f.read(buffer, 1, 2);
                header->insnum = BigEndian::GET_WORD(buffer);
                f.read(buffer, 1, 2);
                header->smpnum = BigEndian::GET_WORD(buffer);

                srcSmp = new DBMSample[header->smpnum];

                f.read(buffer, 1, 2);
                numSubSongs = BigEndian::GET_WORD(buffer);

                // Allocate order list table
                orderLists = new mp_uword *[numSubSongs];
                orderListLengths = new mp_uword[numSubSongs];

                f.read(buffer, 1, 2);
                header->patnum = BigEndian::GET_WORD(buffer);

                patterns = new mp_ubyte *[header->patnum];

                f.read(buffer, 1, 2);
                header->channum = BigEndian::GET_WORD(buffer);
                break;

            case 0x534F4E47:    // 'SONG'
            {
                // Skip name
                for (i = 0; i < numSubSongs; i++)
                {
                    mp_ubyte name[44];
                    f.read(name, 1, 44);

                    f.read(buffer, 1, 2);
                    orderListLengths[i] = BigEndian::GET_WORD(buffer);
                    orderLists[i] = new mp_uword[BigEndian::GET_WORD(buffer)];

                    for (j = 0; j < orderListLengths[i]; j++)
                    {
                        f.read(buffer, 1, 2);
                        orderLists[i][j] = BigEndian::GET_WORD(buffer);
                    }
                }
                break;
            }

            case 0x494E5354:    // 'INST'
            {
                mp_uword insNum = chunkLen / 50;

                srcIns = new DBMInstrument[insNum];

                for (i = 0; i < insNum; i++)
                {
                    f.read(srcIns[i].name, 1, 30);
                    f.read(buffer, 2, 1);
                    srcIns[i].sampnum = BigEndian::GET_WORD(buffer);
                    f.read(buffer, 2, 1);
                    srcIns[i].volume = BigEndian::GET_WORD(buffer);
                    f.read(buffer, 4, 1);
                    srcIns[i].finetune = BigEndian::GET_DWORD(buffer);
                    f.read(buffer, 4, 1);
                    srcIns[i].repstart = BigEndian::GET_DWORD(buffer);
                    f.read(buffer, 4, 1);
                    srcIns[i].replen = BigEndian::GET_DWORD(buffer);
                    f.read(buffer, 2, 1);
                    srcIns[i].panning = BigEndian::GET_WORD(buffer);
                    f.read(buffer, 2, 1);
                    srcIns[i].flags = BigEndian::GET_WORD(buffer);

                    srcIns[i].venvnum = srcIns[i].penvnum = -1;
                }
                break;
            }

            case 0x50415454:    // 'PATT'
            {
                for (i = 0; i < header->patnum; i++)
                {
                    f.read(buffer, 2, 1);
                    phead[i].rows = BigEndian::GET_WORD(buffer);
                    f.read(buffer, 4, 1);
                    phead[i].len = BigEndian::GET_DWORD(buffer);

                    patterns[i] = new mp_ubyte[phead[i].len];
                    f.read(patterns[i], 1, phead[i].len);
                }
                break;
            }

            case 0x534D504C:    // 'SMPL'
            {
                for (i = 0; i < header->smpnum; i++)
                {
                    f.read(buffer, 4, 1);
                    srcSmp[i].flags = BigEndian::GET_DWORD(buffer);
                    f.read(buffer, 4, 1);
                    srcSmp[i].samplen = BigEndian::GET_DWORD(buffer);

                    if (srcSmp[i].flags == 1)
                    {
                        srcSmp[i].sample = new mp_ubyte[srcSmp[i].samplen];
                        module->loadSample(f, srcSmp[i].sample, srcSmp[i].samplen, srcSmp[i].samplen);
                    }
                    else if (srcSmp[i].flags == 2)
                    {
                        srcSmp[i].sample = new mp_ubyte[srcSmp[i].samplen * 2];
                        module->loadSample(f, srcSmp[i].sample, srcSmp[i].samplen * 2, srcSmp[i].samplen, XModule::ST_16BIT | XModule::ST_BIGENDIAN);
                    }
                    else
                    {
                        #ifdef VERBOSE
                        printf("Unsupported sample type");
                        #endif
                    }
                }
                break;
            }

            case 0x56454E56:    // 'VENV'
            {
                f.read(buffer, 2, 1);
                mp_uword numEnvelopes = BigEndian::GET_WORD(buffer);
                for (i = 0; i < numEnvelopes; i++)
                {
                    f.read(buffer, 2, 1);
                    mp_uword index = BigEndian::GET_WORD(buffer);

                    if (index)
                        srcIns[index - 1].venvnum = module->numVEnvs;

                    mp_ubyte env[134];
                    f.read(env, 1, 134);

                    TEnvelope venv;
                    convertDBMEnvelope(venv, env);

                    if (!module->addVolumeEnvelope(venv))
                    {
                        CLEAN_DBM;
                        return -7;
                    }
                }
                break;
            }

            case 0x50454E56:    // 'PENV'
            {
                f.read(buffer, 2, 1);
                mp_uword numEnvelopes = BigEndian::GET_WORD(buffer);
                for (i = 0; i < numEnvelopes; i++)
                {
                    f.read(buffer, 2, 1);
                    mp_uword index = BigEndian::GET_WORD(buffer);

                    if (index)
                        srcIns[index - 1].penvnum = module->numPEnvs;

                    mp_ubyte env[134];
                    f.read(env, 1, 134);

                    TEnvelope penv;
                    convertDBMEnvelope(penv, env);

                    if (!module->addPanningEnvelope(penv))
                    {
                        CLEAN_DBM;
                        return -7;
                    }
                }
                break;
            }

            default:
                f.seekWithBaseOffset(f.posWithBaseOffset() + chunkLen);

        }
    }

    // Convert orderlist, subsongs are not supported yet
    j = 0;
    for (i = 0; i < orderListLengths[j]; i++)
    {
        if (i < 256)
            header->ord[i] = (mp_ubyte)orderLists[j][i];
    }

    header->ordnum = orderListLengths[j];

    header->mainvol = 255;
    header->speed = 125;
    header->tempo = 6;
    header->flags = XModule::MODULE_OLDPTINSTRUMENTCHANGE | XModule::MODULE_PTNEWINSTRUMENT;
    header->volenvnum = module->numVEnvs;
    header->panenvnum = module->numPEnvs;

    // Convert patterns
    for (i = 0; i < header->patnum; i++)
    {
        phead[i].effnum = 2;
        phead[i].channum = (mp_ubyte)header->channum;

        const mp_sint32 bps = phead[i].effnum * 2 + 2;

        phead[i].patternData = new mp_ubyte[phead[i].rows * header->channum * bps];

        // out of memory?
        if (phead[i].patternData == NULL)
        {
            CLEAN_DBM;
            return -7;
        }

        memset(phead[i].patternData, 0, phead[i].rows * header->channum * bps);

        mp_uint32 currentRow = 0;

        j = 0;

        mp_ubyte* src = patterns[i];

        while (j < (signed)phead[i].len && currentRow < phead[i].rows)
        {
            mp_ubyte pack = src[j];
            j++;
            if (!pack)
                currentRow++;
            else
            {
                mp_ubyte note = 0, ins = 0;
                mp_ubyte eff1 = 0, op1 = 0, eff2 = 0, op2 = 0;

                mp_ubyte channel = pack - 1;

                pack = src[j];
                j++;

                // Note present?
                if (pack & 0x01)
                {
                    if (src[j] == 0x1F)
                        note = XModule::NOTE_OFF;
                    else
                        note = (src[j] >> 4) * 12 + ((src[j] & 0xF) + 1);
                    j++;
                }
                // Instrument present?
                if (pack & 0x02)
                {
                    ins = src[j];
                    j++;
                }
                // Effect 1 present?
                if (pack & 0x04)
                {
                    eff1 = src[j];
                    j++;
                }
                // Operand 1 present?
                if (pack & 0x08)
                {
                    op1 = src[j];
                    j++;
                }
                // Effect 2 present?
                if (pack & 0x10)
                {
                    eff2 = src[j];
                    j++;
                }
                // Operand 1 present?
                if (pack & 0x20)
                {
                    op2 = src[j];
                    j++;
                }

                convertDBMffects(eff1, op1);
                convertDBMffects(eff2, op2);

                mp_ubyte* dstSlot = phead[i].patternData +
                                    currentRow * header->channum * bps + channel * bps;

                *dstSlot++ = note;
                *dstSlot++ = ins;
                *dstSlot++ = eff1;
                *dstSlot++ = op1;
                *dstSlot++ = eff2;
                *dstSlot++ = op2;
            }
        }

    }

    // convert instrument data
    for (i = 0; i < header->insnum; i++)
    {
        memcpy(instr[i].name, srcIns[i].name, MP_MAXTEXT);
        for (j = 0; j < 120; j++)
            instr[i].snum[j] = i;

        j = srcIns[i].sampnum;

        if (j && j <= header->smpnum)
        {
            j--;
            if (srcSmp[j].samplen)
            {
                instr[i].samp = 1;

                smp[i].flags = 1 | (srcIns[i].panning ? 2 : 0);
                smp[i].pan = (srcIns[i].panning ? srcIns[i].panning - 1 : 0x80);
                smp[i].vol = XModule::vol64to255(srcIns[i].volume);

                smp[i].samplen = srcSmp[j].samplen;

                if (srcSmp[j].flags == 1)
                {
                    smp[i].sample = (mp_sbyte*)module->allocSampleMem(smp[i].samplen);
                    memcpy(smp[i].sample, srcSmp[j].sample, smp[i].samplen);
                }
                else if (srcSmp[j].flags == 2)
                {
                    smp[i].sample = (mp_sbyte*)module->allocSampleMem(smp[i].samplen * 2);
                    memcpy(smp[i].sample, srcSmp[j].sample, smp[i].samplen * 2);
                    smp[i].type |= 16;
                }

                XModule::convertc4spd(srcIns[i].finetune, &smp[i].finetune, &smp[i].relnote);

                smp[i].loopstart = srcIns[i].repstart;
                smp[i].looplen = srcIns[i].replen;

                if (((srcIns[i].flags & 3) == 1) && smp[i].looplen)
                    smp[i].type |= 1;
                else if (((srcIns[i].flags & 2) == 2) && smp[i].looplen)
                    smp[i].type |= 2;

                if (srcIns[i].venvnum >= 0)
                    smp[i].venvnum = srcIns[i].venvnum + 1;
                if (srcIns[i].penvnum >= 0)
                    smp[i].penvnum = srcIns[i].penvnum + 1;
            }
        }
    }

    CLEAN_DBM;

    header->smpnum = header->insnum;

    strcpy(header->tracker, "Digibooster Pro");

    module->setDefaultPanning();

    module->postProcessSamples();

    return 0;
}